import json
from tools.nfp_utls import almost_equal, rotate_polygon, get_polygon_bounds, polygon_area,nfp_rectangle
import json
import pyclipper
import copy
from settings import BIN_LENGTH, BIN_WIDTH
import numpy as np

class Mater:
    def __init__(self, control_No = None):
        self.total_area = 1220*2440
        self.movements_list = []
        self.placed_segment_list = []

        self.temp_movements_list = []
        self.temp_placed_segment_list = []

        self.control_No = control_No
        self.set_container(BIN_LENGTH, BIN_WIDTH)

    def set_container(self, bin_length, bin_width ):
        container = {}
        container_cord = [[0, 0], [bin_length, 0], [bin_length, bin_width], [0, bin_width]]
        container['p_id'] = '-1'
        container['length'] = bin_length
        container['width'] = bin_width
        container['area'] = bin_length * bin_width
        container['points'] = [{'x': p[0], 'y': p[1]} for p in container_cord]
        self.container = container

    def ponput_path(self, segment, nfp_cache, segment_cache):
        """
        输入1： 各个零件的piont,
        输入2： 每个零件(包含面板)之间的nfp
        :param solution: ##
        :param nfp_cache: ##
        输出：
        :return: ##
        """
        self.temp_movements_list = [ i for i in self.movements_list]
        self.temp_placed_segment_list = [ i for i in self.placed_segment_list]

        texture_seg = segment['texture']
        if len(self.placed_segment_list) == 0:
            movement = None
            height_base = self.container['width']
            height_inst = segment['width']
            length_inst = segment['length']
            if ( height_inst > height_base or (height_inst < length_inst <= height_base) ) and texture_seg < 1:
                key = json.dumps({'A': '-1', 'B': segment['p_id'], 'A_rotation': 0, 'B_rotation': 90})
                segment01 = segment_cache[key]['B']
                inner_fit_rectangle = nfp_cache.get(key)  ### 获取零件放在面料板，内侧的nfp
                for point in inner_fit_rectangle:
                    if movement is None or (point['x'] - segment01['x'] < movement['x']):  ### 找到nfp最左侧的候选点
                        movement = {
                            'x': point['x'] - segment01['x'],
                            'y': point['y'] - segment01['y'],
                            'p_id': segment['p_id'],
                            'rotation': 90}
            else:
                key = json.dumps({'A': '-1', 'B': segment['p_id'], 'A_rotation': 0, 'B_rotation': 0})
                segment01 = segment_cache[key]['B']
                inner_fit_rectangle = nfp_cache.get(key)  ### 获取零件放在面料板，内侧的nfp
                for point in inner_fit_rectangle:
                    if movement is None or (point['x'] - segment01['x'] < movement['x']):  ### 找到nfp最左侧的候选点
                        movement = {
                            'x': point['x'] - segment01['x'],
                            'y': point['y'] - segment01['y'],
                            'p_id': segment['p_id'],
                            'rotation': 0}
            self.movements_list.append(movement)
            self.placed_segment_list.append(segment01)
            return 1

        push_segment_area = sum([p['length'] * p['width'] for p in self.placed_segment_list])
        single_area = segment['length'] * segment['width']

        contrast_min_area = {0: None, 90: None}
        contrast_movement = {0: None, 90: None}
        contrast_segment01 = {0: None, 90: None}

        if texture_seg < 1:
            rotations_lst =  [0, 90]
        else:
            rotations_lst =  [0]

        for b_rota in rotations_lst:
            key = json.dumps({'A': '-1', 'B': segment['p_id'], 'A_rotation': 0, 'B_rotation': b_rota})
            segment01 = segment_cache[key]['B']
            inner_fit_rectangle = nfp_cache.get(key)
            if inner_fit_rectangle is None:
                continue
            clipper_bin_nfp = list()
            clipper_bin_nfp.append([[p['x'], p['y']] for p in inner_fit_rectangle])
            clipper = pyclipper.Pyclipper()
            j = 0
            for placed_segment in self.placed_segment_list:
                key = json.dumps(
                    {'A': placed_segment['p_id'], 'B': segment['p_id'], 'A_rotation': placed_segment['rotation'],
                     'B_rotation': b_rota})
                nfp = nfp_cache.get(key)
                moved_nfp_bl = [[point['x'] + self.movements_list[j]['x'], point['y'] + self.movements_list[j]['y']] for point
                                in nfp]  ## nfp move
                moved_nfp_bl = pyclipper.CleanPolygon(moved_nfp_bl)  ### 移除moved_nfp_bl上无关的点集
                j = j + 1
                if len(moved_nfp_bl) > 2:
                    clipper.AddPath(moved_nfp_bl, pyclipper.PT_SUBJECT, True)  ### moved_nfp_bl加入主题
            combine_nfp = clipper.Execute(pyclipper.CT_UNION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO)
            clipper = pyclipper.Pyclipper()
            clipper.AddPaths(combine_nfp, pyclipper.PT_CLIP, True)
            clipper.AddPaths(clipper_bin_nfp, pyclipper.PT_SUBJECT, True)  ## segment的内侧nfp 加入主题
            final_nfp = clipper.Execute(pyclipper.CT_DIFFERENCE, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO)
            final_nfp = pyclipper.CleanPolygons(final_nfp)
            ### final_nfp 是 求待放的零件segment与已放了的零件placed_segment的moved_nfp_bl  和 求待放的零件segment的内侧clipper_bin_nfp 的组合有效final_nfp
            for j in range(len(final_nfp) - 1, -1, -1):
                if len(final_nfp[j]) < 3:  ## 检查是否有nfp 小于三
                    final_nfp.pop(j)
            if len(final_nfp) == 0:
                continue
            candidate_bl_position = list()
            for polygon in final_nfp:
                for p in polygon:
                    ### 将所有可能放置的点都存放在一个list里面，这些点就是candidate position
                    candidate_bl_position.append({'x': p[0], 'y': p[1]})

            movement = None
            min_area = None
            min_weight_area = None
            min_x = None
            ### final_nfp 就是候选点
            for p_nf in candidate_bl_position:
                all_points = list()
                for m in range(0, len(self.placed_segment_list)):  ### 已存放的零件进行move
                    for p in self.placed_segment_list[m]['points']:
                        all_points.append({
                            'x': p['x'] + self.movements_list[m]['x'],
                            'y': p['y'] + self.movements_list[m]['y']})
                # path 坐标
                shift_vector = {
                    'x': p_nf['x'] - segment['points'][0]['x'],  ### 候选的piont 进行move
                    'y': p_nf['y'] - segment['points'][0]['y'],
                    'p_id': segment['p_id'],
                    'rotation': b_rota,
                }
                # 找新坐标后的最小矩形
                for point in segment['points']:
                    all_points.append({
                        'x': point['x'] + shift_vector['x'],
                        'y': point['y'] + shift_vector['y']
                    })
                rect_bounds = get_polygon_bounds(all_points)
                rect_length = rect_bounds['length']
                rect_width = rect_bounds['width']
                area = rect_length * rect_width
                weight_area = rect_length * 2 + rect_width
                if area <= 1220 * 2440 and rect_length <= 2440 and rect_width <= 1220:
                    if (min_area is None or area < min_area or almost_equal(min_area, area)):
                    ## and (min_weight_area is None or weight_area < min_weight_area or almost_equal(min_weight_area, weight_area)) :
                    ##   and (min_x is None or shift_vector['x'] <= min_x):
                        ## min_x = shift_vector['x']
                        ## min_weight_area = weight_area
                        min_area = area
                        movement = shift_vector

            contrast_min_area[b_rota] = min_area
            contrast_movement[b_rota] = movement
            contrast_segment01[b_rota] = segment01

        if contrast_min_area[0] is None and contrast_min_area[90] is None:
            return None
        elif contrast_min_area[0] is not None and contrast_min_area[90] is not None:
            if contrast_min_area[0] <= contrast_min_area[90]:
                if contrast_movement[0]:
                    self.movements_list.append(contrast_movement[0])
                    self.placed_segment_list.append(contrast_segment01[0])
                    fina_min_area = contrast_min_area[0]
            else:
                if contrast_movement[90]:
                    self.movements_list.append(contrast_movement[90])
                    self.placed_segment_list.append(contrast_segment01[90])
                    fina_min_area = contrast_min_area[90]
        elif contrast_min_area[0] is None and contrast_min_area[90] is not None:
            if contrast_movement[90]:
                self.movements_list.append(contrast_movement[90])
                self.placed_segment_list.append(contrast_segment01[90])
                fina_min_area = contrast_min_area[90]
        elif contrast_min_area[0] is not None and contrast_min_area[90] is None:
            if contrast_movement[0]:
                self.movements_list.append(contrast_movement[0])
                self.placed_segment_list.append(contrast_segment01[0])
                fina_min_area = contrast_min_area[0]

        min_area_sum = sum([p['length'] * p['width'] for p in self.placed_segment_list])
        pure_fitness = min_area_sum / (1220 * 2440)
        result = {'placements': self.movements_list,
                  'placed_segment_list': self.placed_segment_list,
                  'pure_fitness': pure_fitness,
                  'fitness': fina_min_area / (1220 * 2440)
                  }
        assert result['fitness'] <= 1.0
        assert result['pure_fitness'] <= 1.0

        use_ratio = (push_segment_area + single_area) / fina_min_area
        use_ratio02 = min_area_sum / fina_min_area
        assert use_ratio == use_ratio02

        return use_ratio


